package com.fm.webreader.html.table;

import com.fm.base.util.Pair;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class HtmlTable<V> {
	private ArrayList<ArrayList<HtmlTd<V>>> rows = new ArrayList<>();

	public int getHeight() {
		return rows.size();
	}

	/**
	 * @param row   0 base
	 * @param value value to insert
	 */
	public void addRowElement(int row, V value) {
		addRowElement(row, value, 1, 1);
	}

	/**
	 * @param row     0 base
	 * @param value   value to insert
	 * @param rowspan rowspan
	 * @param colspan colspan
	 */
	public void addRowElement(int row, V value, int rowspan, int colspan) {
		for (int r = rows.size(); r <= row; ++r) {
			rows.add(new ArrayList<>());
		}
		ArrayList<HtmlTd<V>> list = rows.get(row);
		for (int col = 0; col < list.size(); col++) {
			if (list.get(col) == null) {
				HtmlTd<V> td = new HtmlTd<>(value, new Pair<>(row, row + rowspan - 1), new Pair<>(col, col + colspan - 1));
				setElement(td, row, col, rowspan, colspan);
				return;
			}
		}
		int col = list.size();
		HtmlTd<V> td = new HtmlTd<>(value, new Pair<>(row, row + rowspan - 1), new Pair<>(col, col + colspan - 1));
		setElement(td, row, col, rowspan, colspan);
	}

	private void setElement(HtmlTd<V> td, int row, int col, int rowspan, int colspan) {
		for (int r = 0; r < rowspan; r++) {
			if (rows.size() <= r + row) {
				rows.add(new ArrayList<>());
			}
			ArrayList<HtmlTd<V>> curRow = rows.get(row + r);
			for (int c = curRow.size(); c < col + colspan; c++) {
				curRow.add(null);
			}
			for (int c = col; c < col + colspan; c++) {
				curRow.set(c, td);
			}
		}
	}

	public List<HtmlTd<V>> getTdRow(int rowNum, boolean needFirst, boolean heightSensitive) {
		List<HtmlTd<V>> need = new LinkedList<>();
		Iterator<HtmlTd<V>> row = rows.get(rowNum).iterator();
		HtmlTd<V> first = null;
		if (row.hasNext()) {
			first = row.next();
		}
		if (first == null) {
			throw new RuntimeException("the row:" + rowNum + " must have first td");
		}
		if (needFirst) {
			need.add(first);
		}
		HtmlTd<V> pre = first;
		while (row.hasNext()) {
			HtmlTd<V> td = row.next();
			if (td == null) {
				if (heightSensitive) {
					if (first.rowInterval.getLeft().equals(first.rowInterval.getRight())) {
						need.add(null);
					}
				} else {
					need.add(null);
				}
			} else {
				if (td == pre) {
					continue;
				}
				if (heightSensitive) {
					if (first.rowInterval.getLeft().equals(td.rowInterval.getLeft()) &&
						first.rowInterval.getRight().equals(td.rowInterval.getRight())) {
						need.add(td);
					}
				} else {
					need.add(td);
				}
				pre = td;
			}
		}
		return need;
	}

	public List<V> getRow(int rowNum, boolean needFirst, boolean heightSensitive) {
		return getTdRow(rowNum, needFirst, heightSensitive).stream()
			.map(td -> td.value)
			.collect(Collectors.toCollection(LinkedList::new));
	}

	public List<HtmlTd<V>> getTdCol(int colNum, boolean needFirst, boolean heightSensitive) {
		List<HtmlTd<V>> need = new LinkedList<>();
		HtmlTd<V> first = rows.get(0).get(colNum);
		if (first == null) {
			throw new RuntimeException("the col:" + colNum + " must have first td");
		}
		if (needFirst) {
			need.add(first);
		}
		HtmlTd<V> pre = first;
		for (int i = 1; i < rows.size(); i++) {
			HtmlTd<V> td = rows.get(i).get(colNum);
			if (td == null) {
				if (heightSensitive) {
					if (first.colInterval.getLeft().equals(first.colInterval.getRight())) {
						need.add(null);
					}
				} else {
					need.add(null);
				}
			} else {
				if (td == pre) {
					continue;
				}
				if (heightSensitive) {
					if (first.colInterval.getLeft().equals(td.colInterval.getLeft()) &&
						first.colInterval.getRight().equals(td.colInterval.getRight())) {
						need.add(td);
					}
				} else {
					need.add(td);
				}
				pre = td;
			}
		}
		return need;
	}

	public List<V> getCol(int colNum, boolean needFirst, boolean heightSensitive) {
		return getTdCol(colNum, needFirst, heightSensitive).stream()
			.map(td -> td.value)
			.collect(Collectors.toCollection(LinkedList::new));
	}

	public List<HtmlTd<V>> getTdColBelow(HtmlTd<V> td) {
		List<HtmlTd<V>> need = new LinkedList<>();
		int rowNum = td.getRowInterval().getRight() + 1;
		while (rowNum < rows.size()) {
			List<HtmlTd<V>> row = rows.get(rowNum);
			if (row.size() > td.getColInterval().getRight()) {
				HtmlTd<V> cell = row.get(td.getColInterval().getLeft());
				if (cell == null) {
					break;
				} else {
					if (cell.getColInterval().equals(td.getColInterval())) {
						need.add(cell);
						rowNum = cell.getRowInterval().getRight() + 1;
					} else {
						break;
					}
				}
			} else {
				break;
			}
		}
		return need;
	}

	public List<HtmlTd<V>> getTdRowFollow(HtmlTd<V> td) {
		List<HtmlTd<V>> need = new LinkedList<>();
		List<HtmlTd<V>> row = rows.get(td.getRowInterval().getLeft());
		int colNum = td.getColInterval().getRight() + 1;
		while (colNum < row.size()) {
			HtmlTd<V> cell = row.get(colNum);
			if (cell == null) {
				break;
			} else {
				if (cell.getRowInterval().equals(td.getRowInterval())) {
					need.add(cell);
					colNum = cell.getColInterval().getRight() + 1;
				} else {
					break;
				}
			}
		}
		return need;
	}

	@Override
	public String toString() {
		return StringUtils.join(rows, "\n");
	}

	@Override
	public int hashCode() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean equals(Object obj) {
		throw new UnsupportedOperationException();
	}
}
